package com.bkhech.home.practice.javacore.fileupload;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * FileUploadUtils
 * 文件上传，支持断点续传
 * <p>
 * 思想：
 * 分片思想
 * <p>
 * 参考：{@literal <a href="https://blog.csdn.net/qq_63431773/article/details/133468292">...</a>}
 *
 * @author guowm
 * @date 2024/7/2
 */
public class FileUploadUtils {
    /**
     * 支持断点续传
     *
     * @param src       拷贝的原文件
     * @param dest      拷贝的位置
     * @param threadNum 开启的线程数
     * @throws Exception
     */
    public static void transportFile(File src, File dest, int threadNum) throws Exception {
        // 存储多个线程、用于阻塞主线程
        List<CompletableFuture<Void>> futureList = new ArrayList<>();
        final ExecutorService executors = Executors.newFixedThreadPool(threadNum);
        // 定义一个基于多线程 的 hashmap 高并发 map, 用来存储当前上传进度数据
        final Map<Integer, Long> process = new ConcurrentHashMap<>();
        // 每一个线程读取的大小，向上取整
        Long part = (long) Math.ceil((double) src.length() / threadNum);
        String logName = dest.getCanonicalPath() + ".log";
        final String[] logData = getLogData(logName);
        // 临时目标文件
        final File destTmp = new File(dest.getCanonicalPath() + ".downloading");

        final FileMetadata.FileMetadataBuilder fileMetadataBuilder = FileMetadata.builder()
                .src(src).dest(dest).destTmp(destTmp)
                .part(part).process(process)
                .logName(logName).logData(logData);

        for (int i = 0; i < threadNum; i++) {
            final FileMetadata fileMetadata = fileMetadataBuilder.fileIndex(i).build();
            final CompletableFuture<Void> future = CompletableFuture.runAsync(new FileWorker(fileMetadata), executors);
            futureList.add(future);
        }

        // 阻塞主线程，直到所有任务全部执行完成
        CompletableFuture.allOf((futureList.toArray(new CompletableFuture[0]))).get();

        // 读取完成后, 更名、将日志文件删除
        final boolean renameRet = destTmp.renameTo(dest);
        if (renameRet) {
            boolean deleted = destTmp.delete();
            boolean delete = new File(logName).delete();
        }
    }

    /**
     * 读取日志文件中的数据
     *
     * @param logName
     * @return
     * @throws IOException
     */
    private static String[] getLogData(String logName) throws IOException {
        String[] logData = null;
        File logFile = new File(logName);
        if (logFile.exists()) {
            BufferedReader reader = new BufferedReader(new FileReader(logFile));
            String data = reader.readLine();
            // 拆分 字符串
            logData = data.split(",");
            reader.close();
        }
        return logData;
    }

    /**
     * 支持断点续传
     *
     * @param src  拷贝的原文件
     * @param dest 拷贝的位置
     * @throws Exception
     */
    public static void transportFile(File src, File dest) throws Exception {
        transportFile(src, dest, 5);
    }

    public static void transportFile(String src, String dest) throws Exception {
        transportFile(new File(src), new File(dest));
    }

    public static void transportFile(String src, String dest, int threadNum) throws Exception {
        transportFile(new File(src), new File(dest), threadNum);
    }
}